from fastapi import FastAPI, Depends, HTTPException, Request, Form
from sqlalchemy.sql import func
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from fastapi import FastAPI, Depends, HTTPException, WebSocket, WebSocketDisconnect
from sqlalchemy.orm import Session
from models import Base, UploadedImage, engine, SessionLocal, User, Message, Chat, UserProfileModel
from pydantic import BaseModel
from passlib.context import CryptContext
from typing import List
import uvicorn
from fastapi.middleware.cors import CORSMiddleware

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
import shutil
import os

import uuid
from datetime import datetime

app = FastAPI()

origins = ["*"]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Base.metadata.create_all(bind=engine)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

templates = Jinja2Templates(directory="templates")

class UserCreate(BaseModel):
    username: str
    password: str

class UserLogin(BaseModel):
    username: str
    password: str

class ProfileResponse(BaseModel):
    first_name: str
    last_name: str
    profile_photo: str

class UserResponse(BaseModel):
    id: int
    username: str
    profile: ProfileResponse

    class Config:
        orm_mode = True

class UserProfile(BaseModel):
    first_name: str
    last_name: str
    profile_photo: str

class UserProfileResponse(BaseModel):
    user_id: int
    first_name: str
    last_name: str
    profile_photo: str
    last_seen: str

    class Config:
        orm_mode = True

class MessageCreate(BaseModel):
    sender_id: int
    receiver_id: int
    chat_id: int
    content: str

class MessageResponse(BaseModel):
    id: int
    sender_id: int
    receiver_id: int
    content: str
    timestamp: str

class ChatCreate(BaseModel):
    user1_id: int
    user2_id: int

class ChatResponse(BaseModel):
    id: int
    user1_id: int
    user2_id: int
    name: str
    profileImage: str
    last_message: str
    timestamp: str
    is_read: bool

    class Config:
        orm_mode = True

class LastMessageUpdate(BaseModel):
    last_message: str
    timestamp: str


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/login/")
def login(user: UserLogin, db: Session = Depends(get_db)):
    db_user = db.query(User).filter(User.username == user.username).first()
    if not db_user or not pwd_context.verify(user.password, db_user.password):
        raise HTTPException(status_code=400, detail="Invalid credentials")
    return {"message": "Login successful", "userId": db_user.id}

@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    hashed_password = pwd_context.hash(user.password)
    db_user = User(username=user.username, password=hashed_password)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    db_profile = UserProfileModel(user_id=db_user.id, first_name="", last_name="", profile_photo="")
    db.add(db_profile)
    db.commit()
    db.refresh(db_profile)
    return UserResponse(
        id=db_user.id,
        username=db_user.username,
        profile=ProfileResponse(
            first_name=db_profile.first_name,
            last_name=db_profile.last_name,
            profile_photo=db_profile.profile_photo
        )
    )

@app.get("/users/", response_model=List[UserResponse])
def get_users(db: Session = Depends(get_db)):
    users = db.query(User).all()
    result = []
    for user in users:
        profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == user.id).first()
        user_response = UserResponse(
            id=user.id,
            username=user.username,
            profile=ProfileResponse(
                first_name=profile.first_name,
                last_name=profile.last_name,
                profile_photo=profile.profile_photo
            ) if profile else None
        )
        result.append(user_response)
    return result

@app.put("/users/{user_id}/profile", response_model=UserProfileResponse)
def update_user_profile(user_id: int, profile_update: UserProfile, db: Session = Depends(get_db)):
    try:
        db_user = db.query(User).filter(User.id == user_id).first()
        if not db_user:
            raise HTTPException(status_code=404, detail="User not found")

        db_profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == user_id).first()
        if not db_profile:
            db_profile = UserProfileModel(user_id=user_id, first_name=profile_update.first_name, last_name=profile_update.last_name, profile_photo=profile_update.profile_photo)
            db.add(db_profile)
        else:
            db_profile.first_name = profile_update.first_name
            db_profile.last_name = profile_update.last_name
            db_profile.profile_photo = profile_update.profile_photo

        db.commit()
        db.refresh(db_profile)

        return UserProfileResponse(
            user_id=db_profile.user_id,
            first_name=db_profile.first_name,
            last_name=db_profile.last_name,
            profile_photo=db_profile.profile_photo,
            last_seen=str(db_user.last_seen))
    except Exception as e:
        # Логирование ошибки для дальнейшего анализа
        print(f"Failed to update user profile for user ID {user_id}: {str(e)}")
        return {"success": False, "error": str(e)}

@app.get("/users/{user_id}/profile", response_model=UserProfileResponse)
def get_user_profile(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == user_id).first()
    if profile is None:
        raise HTTPException(status_code=404, detail="Profile not found")
    return UserProfileResponse(
        user_id=user.id,
        first_name=profile.first_name,
        last_name=profile.last_name,
        profile_photo=profile.profile_photo,
        last_seen=getTime()   # Преобразование datetime в строку
    )

@app.put("/chats/{chat_id}/last_message")
def update_last_message(chat_id: int, last_message_update: LastMessageUpdate, db: Session = Depends(get_db)):
    chat = db.query(Chat).filter(Chat.id == chat_id).first()
    if not chat:
        raise HTTPException(status_code=404, detail="Chat not found")
    
    logging.info(f"Updating chat ID {chat_id} with last_message: {last_message_update.last_message}, timestamp: {last_message_update.timestamp}")
    chat.last_message = last_message_update.last_message
    chat.timestamp = last_message_update.timestamp

    try:
        db.commit()
        logging.info(f"Chat ID {chat_id} successfully updated in the database")
    except Exception as e:
        db.rollback()
        logging.error(f"Failed to update chat ID {chat_id}: {e}")
        raise HTTPException(status_code=500, detail="Failed to update last message")

    return {"message": "Last message updated successfully"}



import logging

@app.post("/messages/", response_model=MessageResponse)
def create_message(message_create: MessageCreate, db: Session = Depends(get_db)):
    db_message = Message(
        sender_id=message_create.sender_id,
        receiver_id=message_create.receiver_id,
        chat_id=message_create.chat_id,
        content=message_create.content
    )
    db.add(db_message)
    db.commit()
    db.refresh(db_message)
    
    # Логирование
    logging.info(f"Создано сообщение ID {db_message.id} в чате {db_message.chat_id}")

    # Обновление последнего сообщения в чате
    chat = db.query(Chat).filter(Chat.id == message_create.chat_id).first()
    if chat:
        chat.last_message = db_message.content
        chat.timestamp = db_message.timestamp
        db.commit()
        logging.info(f"Обновлено последнее сообщение для чата ID {chat.id}: {chat.last_message} на {chat.timestamp}")
    
    return MessageResponse(
        id=db_message.id,
        sender_id=db_message.sender_id,
        receiver_id=db_message.receiver_id,
        content=db_message.content,
        timestamp=str(db_message.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
    )




@app.get("/messages/{chat_id}", response_model=List[MessageResponse])
def get_messages(chat_id: int, db: Session = Depends(get_db)):
    messages = db.query(Message).filter(Message.chat_id == chat_id).all()
    return [
        MessageResponse(
            id=message.id,
            sender_id=message.sender_id,
            receiver_id=message.receiver_id,
            content=message.content,
            timestamp=str(message.timestamp.strftime("%Y-%m-%d %H:%M:%S"))
        ) for message in messages
    ]

@app.post("/chats/", response_model=ChatResponse)
def create_chat(chat: ChatCreate, db: Session = Depends(get_db)):
    existing_chat = db.query(Chat).filter(
        ((Chat.user1_id == chat.user1_id) & (Chat.user2_id == chat.user2_id)) |
        ((Chat.user1_id == chat.user2_id) & (Chat.user2_id == chat.user1_id))
    ).first()
    if existing_chat:
        last_message = db.query(Message).filter(Message.chat_id == existing_chat.id).order_by(Message.timestamp.desc()).first()
        chat_user = db.query(User).filter(User.id == (existing_chat.user1_id if existing_chat.user1_id != chat.user1_id else existing_chat.user2_id)).first()
        profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == chat_user.id).first()
        return ChatResponse(
            id=existing_chat.id,
            user1_id=existing_chat.user1_id,
            user2_id=existing_chat.user2_id,
            name=chat_user.username,
            profileImage=profile.profile_photo,
            last_message=last_message.content if last_message else "",
            timestamp=(last_message.timestamp.strftime("%Y-%m-%d %H:%M:%S")) if last_message else "",
            is_read=last_message.sender_id != chat.user1_id if last_message else True
        )
    db_chat = Chat(user1_id=chat.user1_id, user2_id=chat.user2_id)
    db.add(db_chat)
    db.commit()
    db.refresh(db_chat)
    chat_user = db.query(User).filter(User.id == (db_chat.user1_id if db_chat.user1_id != chat.user1_id else db_chat.user2_id)).first()
    profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == chat_user.id).first()
    return ChatResponse(
        id=db_chat.id,
        user1_id=db_chat.user1_id,
        user2_id=db_chat.user2_id,
        name=chat_user.username,
        profileImage=profile.profile_photo,
        last_message="_",
        timestamp="",
        is_read=True
    )

@app.get("/chats/{user_id}", response_model=List[ChatResponse])
def get_chats(user_id: int, db: Session = Depends(get_db)):
    chats = db.query(Chat).filter((Chat.user1_id == user_id) | (Chat.user2_id == user_id)).all()
    chat_list = []

    for chat in chats:
        last_message = db.query(Message).filter(Message.chat_id == chat.id).order_by(Message.timestamp.desc()).first()
        chat_user = db.query(User).filter(User.id == (chat.user1_id if chat.user1_id != user_id else chat.user2_id)).first()
        profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == chat_user.id).first()
        chat_list.append(ChatResponse(
            id=chat.id,
            user1_id=chat.user1_id,
            user2_id=chat.user2_id,
            name=f"{profile.first_name} {profile.last_name}",
            profileImage=profile.profile_photo,
            last_message=last_message.content if last_message else "",
            timestamp=str(last_message.timestamp.strftime("%Y-%m-%d %H:%M:%S")) if last_message else "",
            is_read=last_message.sender_id != user_id if last_message else True
        ))

    logging.info(f"Получены чаты для пользователя ID {user_id}: {chat_list}")

    return chat_list




@app.put("/users/{user_id}/last_seen")
def update_last_seen(user_id: int, db: Session = Depends(get_db)):
    db_user = db.query(User).filter(User.id == user_id).first()
    if not db_user:
        raise HTTPException(status_code=404, detail="User not found")
    db_user.last_seen = getTime() 
    db.commit()
    return {"message": "Last seen updated"}

# --- ADMIN PANEL ---
@app.get("/admin/login", response_class=HTMLResponse)
def admin_login_page(request: Request):
    return templates.TemplateResponse("login.html", {"request": request})

@app.post("/admin/login", response_class=HTMLResponse)
def admin_login(request: Request, password: str = Form(...)):
    if password == "admin":
        response = RedirectResponse(url="/admin/users", status_code=302)
        response.set_cookie(key="admin", value="true")
        return response
    else:
        return templates.TemplateResponse("login.html", {"request": request, "error": "Invalid password"})

def verify_admin(request: Request):
    if request.cookies.get("admin") != "true":
        raise HTTPException(status_code=403, detail="Not authenticated")

@app.get("/admin/users", response_class=HTMLResponse)
def admin_get_users(request: Request, db: Session = Depends(get_db)):
    verify_admin(request)
    users = db.query(User).all()
    return templates.TemplateResponse("users.html", {"request": request, "users": users})

@app.get("/admin/messages", response_class=HTMLResponse)
def admin_get_messages(request: Request, db: Session = Depends(get_db)):
    verify_admin(request)
    messages = db.query(Message).all()
    return templates.TemplateResponse("messages.html", {"request": request, "messages": messages})

@app.get("/admin/chats", response_class=HTMLResponse)
def admin_get_chats(request: Request, db: Session = Depends(get_db)):
    verify_admin(request)
    chats = db.query(Chat).all()
    return templates.TemplateResponse("chats.html", {"request": request, "chats": chats})

from fastapi.staticfiles import StaticFiles

app.mount("/uploaded_images", StaticFiles(directory="uploaded_images"), name="uploaded_images")


@app.get("/admin/images", response_class=HTMLResponse)
def admin_get_images(request: Request, db: Session = Depends(get_db)):
    verify_admin(request)
    images = db.query(UploadedImage).all()
    return templates.TemplateResponse("images.html", {"request": request, "images": images})


# Profile Photo Upload Directory
UPLOAD_DIRECTORY = "./uploaded_images/"

# Ensure the upload directory exists
if not os.path.exists(UPLOAD_DIRECTORY):
    os.makedirs(UPLOAD_DIRECTORY)
    logging.info(f"Upload directory created at {UPLOAD_DIRECTORY}")


@app.post("/upload-image/")
async def upload_image(file: UploadFile = File(...), db: Session = Depends(get_db)):
    try:
        unique_filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex}_{file.filename}"
        file_location = os.path.join(UPLOAD_DIRECTORY, unique_filename)
        
        with open(file_location, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)

        # Log the file path
        logging.info(f"File saved to {file_location}")

        # Save image info to the database
        uploaded_image = UploadedImage(filename=unique_filename, file_path=file_location)
        db.add(uploaded_image)
        db.commit()

        return JSONResponse(content={"filePath": file_location})
    except Exception as e:
        logging.error(f"Failed to upload image: {e}")
        raise HTTPException(status_code=500, detail="Failed to upload image")

from fastapi.responses import StreamingResponse

from fastapi.responses import StreamingResponse, FileResponse

import logging

logging.basicConfig(level=logging.INFO)

@app.put("/users/{user_id}/profile", response_model=UserProfileResponse)
def update_user_profile(user_id: int, profile_update: UserProfile, db: Session = Depends(get_db)):
    try:
        db_user = db.query(User).filter(User.id == user_id).first()
        if not db_user:
            raise HTTPException(status_code=404, detail="User not found")

        db_profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == user_id).first()
        if not db_profile:
            db_profile = UserProfileModel(user_id=user_id, first_name=profile_update.first_name, last_name=profile_update.last_name, profile_photo=profile_update.profile_photo)
            db.add(db_profile)
        else:
            db_profile.first_name = profile_update.first_name
            db_profile.last_name = profile_update.last_name
            db_profile.profile_photo = profile_update.profile_photo

        db.commit()
        db.refresh(db_profile)
        
        return UserProfileResponse(
            user_id=db_profile.user_id,
            first_name=db_profile.first_name,
            last_name=db_profile.last_name,
            profile_photo=db_profile.profile_photo,
            last_seen=getTime() 
        )
    except Exception as e:
        logging.error(f"Failed to update user profile for user ID {user_id}: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/users/{user_id}/profile-image")
def get_profile_image(user_id: int, db: Session = Depends(get_db)):
    profile = db.query(UserProfileModel).filter(UserProfileModel.user_id == user_id).first()
    if profile is None or not profile.profile_photo:
        raise HTTPException(status_code=404, detail="Profile photo not found")

    file_path = os.path.join(profile.profile_photo)
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="Profile photo not found")

    return FileResponse(file_path)



# WebSocket logic
connected_clients = {}

@app.websocket("/ws/{chat_id}/{user_id}")
async def websocket_endpoint(websocket: WebSocket, chat_id: int, user_id: int):
    await websocket.accept()
    if chat_id not in connected_clients:
        connected_clients[chat_id] = {}
    connected_clients[chat_id][user_id] = websocket
    
    try:
        while True:
            data = await websocket.receive_text()
            for uid, client in connected_clients[chat_id].items():
                if uid != user_id:
                    await client.send_text(data)
    except WebSocketDisconnect:
        del connected_clients[chat_id][user_id]
        if not connected_clients[chat_id]:
            del connected_clients[chat_id]


def getTime() -> str:
    return datetime.now().strftime('%H:%M')

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
